/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Free Software Foundation, Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.datacleaner.util;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.MatteBorder;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.text.JTextComponent;

import org.apache.metamodel.util.FileHelper;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.widgets.DarkButtonUI;
import org.datacleaner.widgets.DarkToggleButtonUI;
import org.datacleaner.widgets.DefaultButtonUI;
import org.datacleaner.widgets.DefaultToggleButtonUI;
import org.datacleaner.widgets.PrimaryButtonUI;
import org.datacleaner.widgets.table.DCTablePanel;
import org.datacleaner.windows.ErrorDialog;
import org.jdesktop.swingx.JXErrorPane;
import org.jdesktop.swingx.border.DropShadowBorder;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.error.ErrorInfo;
import org.jdesktop.swingx.error.ErrorLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class WidgetUtils {

    private static final Logger logger = LoggerFactory.getLogger(WidgetUtils.class);

    private static final WidgetScreenResolutionAdjuster adjuster = WidgetScreenResolutionAdjuster.get();

    public static final float FONT_SIZE_SMALL = adjuster.adjust(11f);

    private static final Map<String, Font> fonts;

    private static final Font FONT_MULI_PLAIN;
    private static final Font FONT_MULI_BOLD;
    private static final Font FONT_MULI_LIGHT;

    public static final Font FONT_FONTAWESOME;

    static {
        fonts = new HashMap<>();

        final Font[] fontArray = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        for (final Font font : fontArray) {
            if (font.isPlain()) {
                fonts.put(font.getName(), font);
            }
        }

     /*   FONT_MULI_PLAIN = createFont("fonts/Muli.ttf");
        FONT_MULI_BOLD = createFont("fonts/Muli-Bold.ttf");
        FONT_MULI_LIGHT = createFont("fonts/Muli-Light.ttf");

        FONT_FONTAWESOME = createFont("fonts/FontAwesome-4.3.0.ttf").deriveFont(14f);*/

        FONT_MULI_PLAIN = new Font("宋体",0,11);//createFont("fonts/Muli.ttf");
        FONT_MULI_BOLD = new Font("宋体",1,11);//createFont("fonts/Muli-Bold.ttf");
        FONT_MULI_LIGHT = new Font("宋体",0,11);//createFont("fonts/Muli-Light.ttf");


        FONT_FONTAWESOME = createFont("fonts/FontAwesome-4.3.0.ttf").deriveFont(14f);
        fonts.put(FONT_MULI_PLAIN.getName(), FONT_MULI_PLAIN);
    }

    public static final Font FONT_BANNER = FONT_MULI_LIGHT.deriveFont(adjuster.adjust(21f));
    public static final Font FONT_HEADER1 = FONT_MULI_LIGHT.deriveFont(adjuster.adjust(18f));
    public static final Font FONT_HEADER2 = FONT_MULI_LIGHT.deriveFont(adjuster.adjust(16f));
    public static final Font FONT_MONOSPACE = new FontUIResource("Monospaced", Font.PLAIN, adjuster.adjust(14));
    public static final Font FONT_BUTTON = FONT_MULI_PLAIN.deriveFont(adjuster.adjust(14f));
    public static final Font FONT_NORMAL = FONT_MULI_PLAIN.deriveFont(adjuster.adjust(13f));
    public static final Font FONT_BOLD = FONT_MULI_BOLD.deriveFont(adjuster.adjust(13f));
    public static final Font FONT_SMALL = FONT_MULI_PLAIN.deriveFont(FONT_SIZE_SMALL);
    public static final Font FONT_TABLE_HEADER = FONT_NORMAL.deriveFont(Font.BOLD);

    public static final int SCROLL_UNIT_INCREMENT = adjuster.adjust(20);

    // blue base color of DC styling (#05b9f0)
    public static final Color BG_COLOR_BLUE_MEDIUM = new ColorUIResource(5, 185, 240);
    public static final Color BG_COLOR_BLUE_BRIGHT = slightlyBrighter(BG_COLOR_BLUE_MEDIUM);
    public static final Color BG_COLOR_BLUE_DARK = slightlyDarker(BG_COLOR_BLUE_MEDIUM);

    // green base color of DC styling (#70be44)
    public static final Color BG_COLOR_GREEN_MEDIUM = new ColorUIResource(122, 190, 68);
    public static final Color BG_COLOR_GREEN_BRIGHT = slightlyBrighter(BG_COLOR_GREEN_MEDIUM);
    public static final Color BG_COLOR_GREEN_DARK = slightlyDarker(BG_COLOR_GREEN_MEDIUM);

    // orange base color of DC styling (#f58132)
    public static final Color BG_COLOR_ORANGE_MEDIUM = new ColorUIResource(245, 129, 50);
    public static final Color BG_COLOR_ORANGE_BRIGHT = slightlyBrighter(BG_COLOR_ORANGE_MEDIUM);
    public static final Color BG_COLOR_ORANGE_DARK = slightlyDarker(BG_COLOR_ORANGE_MEDIUM);

    // white with 10% alpha/opacity
    public static final Color BG_SEMI_TRANSPARENT = new ColorUIResource(new Color(0.0f, 0.0f, 0.0f, 0.05f));

    // pale yellow color which work fine for information/help text fields.
    // #f4f4d3
    @Deprecated
    public static final Color BG_COLOR_PALE_YELLOW = new ColorUIResource(244, 244, 211);

    // white
    public static final Color BG_COLOR_BRIGHTEST = new ColorUIResource(Color.WHITE);

    // #e1e1e1 (silver-ish)
    public static final Color BG_COLOR_BRIGHT = new ColorUIResource(245, 245, 245);

    // slightly darker than BRIGHT
    public static final Color BG_COLOR_LESS_BRIGHT = new ColorUIResource(220, 220, 220);

    // #a0a0a0
    public static final Color BG_COLOR_MEDIUM = new ColorUIResource(130, 140, 150);

    public static final Color BG_COLOR_LESS_DARK = new ColorUIResource(55, 55, 55);

    public static final Color BG_COLOR_DARK = new ColorUIResource(33, 33, 33);

    public static final Color BG_COLOR_DARKEST = new ColorUIResource(Color.BLACK);

    public static final Color COLOR_DEFAULT_BACKGROUND = BG_COLOR_BRIGHTEST;
    public static final Color COLOR_WELL_BACKGROUND = BG_COLOR_BRIGHT;
    public static final Color COLOR_ALTERNATIVE_BACKGROUND = BG_COLOR_DARK;

    // additional colors, only intended for special widget coloring such as
    // charts etc.

    // Green: #70be44
    @Deprecated
    public static final Color ADDITIONAL_COLOR_GREEN_BRIGHT = BG_COLOR_GREEN_MEDIUM;

    // Red: #d32424
    public static final Color ADDITIONAL_COLOR_RED_BRIGHT = new ColorUIResource(211, 36, 36);

    // Purple: #d3249c
    public static final Color ADDITIONAL_COLOR_PURPLE_BRIGHT = new ColorUIResource(211, 36, 156);

    // Cyan: #24d1d3
    public static final Color ADDITIONAL_COLOR_CYAN_BRIGHT = new ColorUIResource(36, 209, 211);

    public static final int BORDER_WIDE_WIDTH = pix(4);

    public static final Border BORDER_SHADOW =
            new DropShadowBorder(WidgetUtils.BG_COLOR_DARK, pix(6));

    public static final Border BORDER_WIDE_ALTERNATIVE =
            new LineBorder(COLOR_ALTERNATIVE_BACKGROUND, BORDER_WIDE_WIDTH);
    public static final Border BORDER_WIDE_DEFAULT = new LineBorder(COLOR_DEFAULT_BACKGROUND, BORDER_WIDE_WIDTH);
    public static final Border BORDER_WIDE_WELL = new LineBorder(COLOR_WELL_BACKGROUND, BORDER_WIDE_WIDTH);

    @Deprecated
    public static final Border BORDER_WIDE = BORDER_WIDE_ALTERNATIVE;

    @Deprecated
    public static final Border BORDER_WIDE_BRIGHTEST = BORDER_WIDE_DEFAULT;

    public static final Border BORDER_EMPTY = new EmptyBorder(WidgetUtils.BORDER_WIDE_WIDTH,
            WidgetUtils.BORDER_WIDE_WIDTH, WidgetUtils.BORDER_WIDE_WIDTH, WidgetUtils.BORDER_WIDE_WIDTH);
    public static Border BORDER_TOP_PADDING = new EmptyBorder(pix(10), 0, 0, 0);

    public static final Border BORDER_THIN = new LineBorder(BG_COLOR_LESS_BRIGHT);
    public static final Border BORDER_THIN_DARK = new LineBorder(BG_COLOR_DARK);

    public static final Border BORDER_LIST_ITEM =
            new CompoundBorder(new MatteBorder(0, pix(3), 0, 0, BG_COLOR_BLUE_MEDIUM),
                    new MatteBorder(0, 0, pix(1), 0, WidgetUtils.BG_COLOR_LESS_BRIGHT));

    public static final Border BORDER_LIST_ITEM_LEFT_ONLY =
            new CompoundBorder(new MatteBorder(0, pix(3), 0, 0, BG_COLOR_BLUE_MEDIUM), new EmptyBorder(0, 0, pix(1), 0));

    public static final Border BORDER_LIST_ITEM_HIGHLIGHTED =
            new CompoundBorder(new MatteBorder(0, pix(3), 0, 0, BG_COLOR_BLUE_MEDIUM),
                    new MatteBorder(0, 0, pix(1), 0, WidgetUtils.BG_COLOR_BLUE_MEDIUM));

    public static final Border BORDER_LIST_ITEM_SUBTLE =
            new CompoundBorder(new MatteBorder(0, pix(3), 0, 0, BG_COLOR_BLUE_MEDIUM),
                    new MatteBorder(0, 0, pix(1), 0, WidgetUtils.BG_COLOR_MEDIUM));

    public static final Border BORDER_EMPHASIZE_FIELD = new LineBorder(ADDITIONAL_COLOR_RED_BRIGHT, pix(2), false);
    public static final Border BORDER_INPUT = new CompoundBorder(BORDER_THIN, BORDER_EMPTY);

    public static final Border BORDER_TABLE_PANEL = new MatteBorder(pix(1), pix(1), 0, 0, BG_COLOR_LESS_BRIGHT);

    /**
     * Border for indented button panels where the content has to align with check boxes above or below.
     */
    public static final Border BORDER_CHECKBOX_LIST_INDENTATION =
            new EmptyBorder(pix(1), pix(17), 0, pix(1));

    public static final Border BORDER_BUTTON_DARK =
            new EmptyBorder(BORDER_WIDE_WIDTH, pix(10), BORDER_WIDE_WIDTH, pix(10));

    public static final Border BORDER_BUTTON_DARK_WITH_LINE =
            new CompoundBorder(new LineBorder(BG_COLOR_LESS_DARK, pix(1), false),
                    new EmptyBorder(BORDER_WIDE_WIDTH - pix(1), pix(9), BORDER_WIDE_WIDTH - pix(1), pix(9)));

    public static final Border BORDER_BUTTON_DEFAULT =
            new CompoundBorder(new LineBorder(BG_COLOR_LESS_BRIGHT, pix(1), false),
                    new EmptyBorder(BORDER_WIDE_WIDTH - pix(1), pix(9), BORDER_WIDE_WIDTH - pix(1), pix(9)));
    public static final Border BORDER_BUTTON_PRIMARY =
            new EmptyBorder(BORDER_WIDE_WIDTH, pix(10), BORDER_WIDE_WIDTH, pix(10));

    public static final Object BORDER_MENU_ITEM = new EmptyBorder(pix(2), pix(2), pix(2), pix(2));

    /**
     * A highlighter for coloring odd/even rows in a table
     */
    public static final Highlighter LIBERELLO_HIGHLIGHTER = HighlighterFactory
            .createAlternateStriping(colorBetween(BG_COLOR_BRIGHTEST, BG_COLOR_BRIGHT), BG_COLOR_BRIGHTEST);

    /**
     * Slightly moderated version of COLOR.FACTOR
     */
    private static final double COLOR_SCALE_FACTOR = 0.9;

    // grid bag contraint defaults
    public static final int DEFAULT_PADDING = pix(2);
    public static final int DEFAULT_ANCHOR = GridBagConstraints.WEST;

    /**
     * Commonly used insets - empty
     */
    public static final Insets INSETS_EMPTY = new Insets(0, 0, 0, 0);

    /**
     * The 'caret' character used for "more options" symbol with the {@link #FONT_FONTAWESOME} font.
     */
    @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
    public static final String CHAR_CARET_DOWN = "\uf0d7";

    public static final int DIALOG_WIDTH_NORMAL = adjuster.adjust(600);
    public static final int DIALOG_WIDTH_NARROW = adjuster.adjust(400);
    public static final int DIALOG_WIDTH_WIDE = adjuster.adjust(700);

    private WidgetUtils() {
        // prevent instantiation
    }

    /**
     * Invokes a {@link Runnable} as soon as possible. If this is the swing event dispatch thread, it will be run now,
     * or else later using {@link SwingUtilities#invokeLater(Runnable)}
     *
     * @param runnable
     */
    public static void invokeSwingAction(final Runnable runnable) {
        if (SwingUtilities.isEventDispatchThread()) {
            runnable.run();
        } else {
            SwingUtilities.invokeLater(runnable);
        }
    }

    private static Font createFont(final String path) {
        final URL url = ResourceManager.get().getUrl(path);
        if (url == null) {
            throw new IllegalArgumentException("Font resource not found: " + path);
        }

        InputStream in = null;
        try {
            in = url.openStream();
            return Font.createFont(Font.TRUETYPE_FONT, in);
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        } finally {
            FileHelper.safeClose(in);
        }
    }

    public static void centerOnScreen(final Component component) {
        final Dimension paneSize = component.getSize();
        final Dimension screenSize = component.getToolkit().getScreenSize();
        component.setLocation((screenSize.width - paneSize.width) / 2, (screenSize.height - paneSize.height) / 2);
    }

    /**
     * Adds a component to a panel with a grid bag layout
     *
     * @param comp
     * @param panel
     * @param gridx
     * @param gridy
     * @param width
     * @param height
     * @param anchor
     */
    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int width, final int height, final int anchor) {
        addToGridBag(comp, panel, gridx, gridy, width, height, anchor, DEFAULT_PADDING);
    }

    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int width, final int height, final int anchor, final int padding) {
        addToGridBag(comp, panel, gridx, gridy, width, height, anchor, padding, 0.0, 0.0);
    }

    /**
     * Adds a component to a panel with a grid bag layout
     *
     * @param comp
     * @param panel
     * @param gridx
     * @param gridy
     * @param width
     * @param height
     * @param anchor
     * @param padding
     */
    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int width, final int height, final int anchor, final int padding, final double weightx,
            final double weighty) {
        addToGridBag(comp, panel, gridx, gridy, width, height, anchor, padding, weightx, weighty,
                GridBagConstraints.HORIZONTAL);
    }

    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int width, final int height, final int anchor, final int padding, final double weightx,
            final double weighty, final int fill) {
        LayoutManager layout = panel.getLayout();
        if (!(layout instanceof GridBagLayout)) {
            layout = new GridBagLayout();
            panel.setLayout(layout);
        }
        final GridBagLayout gridBagLayout = (GridBagLayout) layout;
        final GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = gridx;
        constraints.gridy = gridy;
        constraints.gridwidth = width;
        constraints.gridheight = height;
        constraints.weightx = weightx;
        constraints.weighty = weighty;
        constraints.anchor = anchor;
        constraints.fill = fill;
        constraints.insets = new Insets(padding, padding, padding, padding);
        gridBagLayout.addLayoutComponent(comp, constraints);
        panel.add(comp);
    }

    /**
     * Adds a component to a panel with a grid bag layout
     *
     * @param comp
     * @param panel
     * @param gridx
     * @param gridy
     * @param width
     * @param height
     */
    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int width, final int height) {
        addToGridBag(comp, panel, gridx, gridy, width, height, DEFAULT_ANCHOR);
    }

    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int anchor, final double weightx, final double weighty) {
        addToGridBag(comp, panel, gridx, gridy, 1, 1, anchor, DEFAULT_PADDING, weightx, weighty);
    }

    /**
     * Adds a component to a panel with a grid bag layout
     *
     * @param comp
     * @param panel
     * @param gridx
     * @param gridy
     */
    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy) {
        addToGridBag(comp, panel, gridx, gridy, 1, 1);
    }

    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final double weightx, final double weighty) {
        addToGridBag(comp, panel, gridx, gridy, 1, 1, DEFAULT_ANCHOR, DEFAULT_PADDING, weightx, weighty);
    }

    /**
     * Adds a component to a panel with a grid bag layout
     *
     * @param comp
     * @param panel
     * @param gridx
     * @param gridy
     * @param anchor
     */
    public static void addToGridBag(final Component comp, final JPanel panel, final int gridx, final int gridy,
            final int anchor) {
        addToGridBag(comp, panel, gridx, gridy, 1, 1, anchor);
    }

    public static void addAligned(final Container container, final JComponent component) {
        component.setAlignmentX(Component.LEFT_ALIGNMENT);
        component.setAlignmentY(Component.TOP_ALIGNMENT);
        container.add(component);
    }

    public static void showErrorMessage(final String shortMessage, final String detailedMessage) {
        final String finalDetailedMessage = detailedMessage == null ? "" : detailedMessage;
        final String finalShortMessage = shortMessage == null ? "" : shortMessage;
        final ErrorDialog dialog = new ErrorDialog(finalShortMessage, finalDetailedMessage);

        dialog.setModal(true);
        dialog.open();
    }

    public static void showErrorMessage(final String shortMessage, final String detailedMessage,
            final Throwable exception) {
        final Throwable presentedException = ErrorUtils.unwrapForPresentation(exception);
        if (exception == null) {
            showErrorMessage(shortMessage, detailedMessage);
            return;
        }

        final String finalDetailedMessage = detailedMessage == null ? "" : detailedMessage;
        final String finalShortMessage = shortMessage == null ? "" : shortMessage;

        final ErrorInfo info = new ErrorInfo(finalShortMessage, finalDetailedMessage, null, "error", presentedException,
                ErrorLevel.SEVERE, null);
        final JXErrorPane errorPane = new JXErrorPane();
        errorPane.setErrorInfo(info);

        final JDialog dialog = JXErrorPane.createDialog(null, errorPane);

        centerOnScreen(dialog);
        JXErrorPane.setDefaultLocale(Locale.ENGLISH);
        dialog.setLocale(Locale.ENGLISH);
        dialog.setModal(true);
        dialog.setTitle(finalShortMessage);
        dialog.setVisible(true);
        dialog.toFront();
    }

    public static void showErrorMessage(final String shortMessage, final Throwable exception) {
        final StringBuilder sb = new StringBuilder();
        Throwable ex = exception;
        while (ex != null) {
            if (sb.length() != 0) {
                sb.append("\n\n");
            }
            String message = ex.getMessage();
            if (StringUtils.isNullOrEmpty(message)) {
                message = "(" + ex.getClass().getName() + ")";
            }
            sb.append(message);
            ex = ex.getCause();
        }
        showErrorMessage(shortMessage, sb.toString(), exception);
    }

    public static JScrollPane scrollable(final JComponent comp, final int maxHeight) {
        final JScrollPane scrollPane = WidgetUtils.scrolleable(comp);

        if (comp.getPreferredSize().getHeight() > maxHeight) {
            final Dimension preferredSize = comp.getPreferredSize();
            final Dimension newSize = new Dimension(preferredSize.width, maxHeight);
            scrollPane.getViewport().setPreferredSize(newSize);
        }

        return scrollPane;
    }

    public static JScrollPane scrolleable(final JComponent comp) {
        final JScrollPane scroll = new JScrollPane();
        if (comp != null) {
            scroll.setViewportView(comp);
        }
        scroll.getVerticalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT);
        scroll.setOpaque(false);
        scroll.getViewport().setOpaque(false);
        return scroll;
    }

    /**
     * Creates a color that is in between two colors, in terms of RGB balance.
     *
     * @param c1
     * @param c2
     * @return
     */
    public static Color colorBetween(final Color c1, final Color c2) {
        final int red = (c1.getRed() + c2.getRed()) / 2;
        final int green = (c1.getGreen() + c2.getGreen()) / 2;
        final int blue = (c1.getBlue() + c2.getBlue()) / 2;
        return new Color(red, green, blue);
    }

    /**
     * Moderated version of Color.darker()
     *
     * @param color
     * @return
     */
    public static Color slightlyDarker(final Color color) {
        return new Color(Math.max((int) (color.getRed() * COLOR_SCALE_FACTOR), 0),
                Math.max((int) (color.getGreen() * COLOR_SCALE_FACTOR), 0),
                Math.max((int) (color.getBlue() * COLOR_SCALE_FACTOR), 0));
    }

    /**
     * Moderated version of Color.brighter()
     *
     * @param color
     * @return
     */
    @SuppressWarnings("checkstyle:LocalVariableName")
    public static Color slightlyBrighter(final Color color) {
        int r = color.getRed();
        int g = color.getGreen();
        int b = color.getBlue();

        /*
         * From 2D group: 1. black.brighter() should return grey 2. applying brighter to blue will always return blue,
         * brighter 3. non pure color (non zero rgb) will eventually return white
         */
        final int i = (int) (1.0 / (1.0 - COLOR_SCALE_FACTOR));
        if (r == 0 && g == 0 && b == 0) {
            return new Color(i, i, i);
        }

        if (r > 0 && r < i) {
            r = i;
        }
        if (g > 0 && g < i) {
            g = i;
        }
        if (b > 0 && b < i) {
            b = i;
        }

        return new Color(Math.min((int) (r / COLOR_SCALE_FACTOR), 255), Math.min((int) (g / COLOR_SCALE_FACTOR), 255),
                Math.min((int) (b / COLOR_SCALE_FACTOR), 255));
    }

    public static String extractText(final Component comp) {
        if (comp instanceof JLabel) {
            return ((JLabel) comp).getText();
        } else if (comp instanceof JTextComponent) {
            return ((JTextComponent) comp).getText();
        } else if (comp instanceof Container) {
            final Component[] children = ((Container) comp).getComponents();
            final StringBuilder sb = new StringBuilder();
            for (final Component child : children) {
                final String text = extractText(child);
                if (!StringUtils.isNullOrEmpty(text)) {
                    if (sb.length() > 0) {
                        sb.append(' ');
                    }
                    sb.append(text);
                }
            }
            return sb.toString();
        }

        logger.warn("Could not extract text from component: {}", comp);
        return "";
    }

    public static DCPanel decorateWithShadow(final JComponent comp) {
        final boolean outline;
        if (comp instanceof DCTablePanel) {
            // table panels has it's own special outline
            outline = false;
        } else {
            outline = true;
        }

        return decorateWithShadow(comp, outline, 4);
    }

    /**
     * Decorates a JComponent with a nice shadow border. Since not all JComponents handle opacity correctly, they will
     * be wrapped inside a DCPanel, which actually has the border.
     *
     * @param comp
     * @param outline
     * @param margin
     * @return
     */
    public static DCPanel decorateWithShadow(final JComponent comp, final boolean outline, final int margin) {
        final DCPanel panel = new DCPanel();
        panel.setLayout(new BorderLayout());
        Border border = BORDER_SHADOW;
        if (outline) {
            border = new CompoundBorder(border, BORDER_THIN);
        }
        if (margin > 0) {
            border = new CompoundBorder(new EmptyBorder(margin, margin, margin, margin), border);
        }
        panel.setBorder(border);
        panel.add(comp, BorderLayout.CENTER);
        return panel;
    }

    /**
     * Finds a font that is capable of displaying the provided text.
     *
     * @param text the text to display.
     * @param fallbackFont the font to fall back to in case no capable font was found
     * @return a font suitable for displaying the text
     */
    public static Font findCompatibleFont(final String text, final Font fallbackFont) {
        final String[] searchFonts = new String[] { Font.SANS_SERIF, Font.SERIF, "Verdana", "Arial Unicode MS",
                "MS UI Gothic", "MS Mincho", "MS Gothic", "Osaka" };
        for (final String fontName : searchFonts) {
            Font font = fonts.get(fontName);
            if (font == null) {
                font = new Font(fontName, fallbackFont.getStyle(), fallbackFont.getSize());
            }

            if (font.canDisplayUpTo(text) == -1) {
                logger.info("Font '{}' was capable, returning", fontName);
                font = font.deriveFont(fallbackFont.getSize2D());
                return font;
            }
        }

        logger.warn("Didn't find any capable fonts for text '{}'", text);
        return fallbackFont;
    }

    public static void setAppropriateFont(final JLabel label) {
        final String text = label.getText();
        if (text == null) {
            return;
        }
        Font font = label.getFont();
        final int canDisplay = font.canDisplayUpTo(text);
        if (canDisplay != -1) {
            logger.info("Default font ('{}') was unable to display text ('{}'), searching for alternative.",
                    font.getName(), text);

            // if the label contains undisplayable characters, look for a
            // different font able of displaying the characters.
            font = findCompatibleFont(text, font);
            label.setFont(font);
        }
    }

    public static void setPrimaryButtonStyle(final AbstractButton button) {
        button.setUI(PrimaryButtonUI.get());
    }

    public static void setDarkButtonStyle(final AbstractButton button) {
        if (button instanceof JToggleButton) {
            button.setUI(DarkToggleButtonUI.get());
        } else {
            button.setUI(DarkButtonUI.get());
        }
    }

    public static void setDefaultButtonStyle(final AbstractButton button) {
        if (button instanceof JToggleButton) {
            button.setUI(DefaultToggleButtonUI.get());
        } else {
            button.setUI(DefaultButtonUI.get());
        }
    }

    private static int pix(final int size) {
        return adjuster.adjust(size);
    }
}
